Tech

Diary

Lecture

About Me

개발중

Cypress 2

JeongSeulho

2024년 01월 27일

준비중...
클립보드로 복사

0. 들어가며

Cypress를 통한 E2E 테스트에 대해 정리 2편

1. 커스텀 커맨드와 커스텀 쿼리

반복 로직을 명령으로 만들고 사용하는 기능이다, 주로 다음과 같은 상황에서 사용

  1. 동일한 요소를 반복해서 조회
  2. 동일한 내용을 반복해서 검증
  3. 테스트 구동을 위한 환경 설정(로그인 등)

(1) 커스텀 커맨드

  • retry ability 미지원
  • 비동기 동작 가능

(2) 커스텀 쿼리

  • retry ability 지원
  • 동기적으로 동작
  • subject를 받아 내부적으로 체이닝 코드 재시도

(3) 커스텀 커맨드와 커스텀 쿼리의 예시

  • 작성 예시
copy
// cypress/support/commands.js
import '@testing-library/cypress/add-commands';

// 로그인 커스텀 커맨드
Cypress.Commands.add('login', () => {
  const username = 'maria@mail.com';
  const password = '12345';

  // 쿠키, 로컬 스토리지, 세션 스토리지에 있는 정보들을 캐싱
  cy.session(username, () => {
    cy.visit('/login');

    cy.findByLabelText('이메일').type(username);
    cy.findByLabelText('비밀번호').type(password);
    cy.findByLabelText('로그인').click();

    // 캐싱하기 전에 로그인이 성공했는지 확인
    cy.location('pathname').should('eq', '/');
  });

  // 로그인 이후 메인 홈페이지로 이동
  cy.visit('/');
});

// 로그아웃 커스텀 커맨드
Cypress.Commands.add('logout', () => {
  cy.findByRole('button', { name: 'Maria' }).click();
  cy.findByRole('button', { name: '확인' }).click();
});

// 현재 URL 단언 커스텀 커맨드
Cypress.Commands.add('assertUrl', url => {
  cy.url().should('eq', `${Cypress.env('baseUrl')}${url}`);
});

// 특정 인덱스의 상품 카드를 가져오는 커스텀 커맨드
Cypress.Commands.add('getProductCardByIndex', index => {
  return cy.findAllByTestId('product-card').eq(index);
});

// 장바구니 버튼을 가져오는 커스텀 쿼리
Cypress.Commands.addQuery('getCartButton', () => {
  // 비동기이기 때문에 cy.now()로 쿼리(get)을 감싼 후 사용하면 inner function에서 사용 가능 
  const getFn = cy.now('get', `[data-testid="cart-icon"]`);

  // inner function을 리턴하는 형태로 콜백을 사용
  // 체이닝을 위해 subject를 인자로 받음
  return subject => {
    // data-testid="cart-icon" 요소를 조회하는 get 쿼리
    // subject 기준으로
    const btn = getFn(subject);

    return btn;
  };
});
  • 사용 예시
copy
it('성공적으로 로그인 되었을 경우 메인 홈 페이지로 이동하며, 사용자 이름 "Maria"와 장바구니 아이콘이 노출된다', () => {
  // 로그인 커스텀 커맨드 사용
  cy.login();

  // 현재 URL 단언 커스텀 커맨드 사용
  cy.assertUrl('/');

  cy.findByText('Maria').should('exist');
  cy.findByTestId('cart-icon').should('exist');
});

  describe('로그인 시', () => {
    // cy.session을 사용하여 처음 로그인을 한번만 수행하면 이후로 캐싱하여 사용
    beforeEach(() => {
      cy.login();
    });

    ...
  });

2. E2E 테스트에서 모킹

회원가입을 테스트 할 때, 몇 가지 문제가 발생

  1. 이전의 가입한 테스트 계정과 매번 중복되지 않는 계정을 생성해야 함
  2. 계속 가입을 하다보면 DB에 불필요한 데이터가 쌓임

이러한 문제로 인하여 회원가입과 탈퇴를 같이 한 프로세스로 테스트하는 것이 좋으나 회원가입만 성공하고 탈퇴가 실패하는 경우가 있을 수 있음
이렇게 불가피한 경우 cy.intercept로 모킹하여 테스트를 진행
이외에도 다양한 상황에서 모킹을 사용할 수 있음

(1) E2E 테스트에서 모킹을 사용하는 경우

  1. 실패 케이스를 테스트하는 경우
  2. 서드파티 API 또는 외부 서비스를 사용하는 경우
  3. DB에 영향을 주는 경우

(2) cy.intercept 사용 예시

copy
it('성공적으로 회원 가입이 완료되었을 경우 "가입 성공!"문구가 노출되며 로그인 페이지로 이동한다', () => {
  cy.intercept('POST', 'http://localhost:3000/users', { statusCode: 200 });

  cy.findByLabelText('이름').type('hanjung');
  cy.findByLabelText('이메일').type('han@email.com');
  cy.findByLabelText('비밀번호').type('password123');

  cy.findByText('가입').click();

  cy.findByText('가입 성공!').should('exist');
  cy.assertUrl('/login');
});

it('회원 가입이 실패했을 경우 "잠시 문제가 발생했습니다! 다시 시도해 주세요." 문구가 노출된다', () => {
  cy.intercept('POST', 'http://localhost:3000/users', { statusCode: 401 });

  cy.findByLabelText('이름').type('hanjung');
  cy.findByLabelText('이메일').type('han@email.com');
  cy.findByLabelText('비밀번호').type('password123');

  cy.findByText('가입').click();

  cy.findByText('잠시 문제가 발생했습니다! 다시 시도해 주세요.').should(
    'exist',
  );
});

3. cypress Github Action에 적용하기

copy
# .github/workflows/cypress.yml
name: 'Cypress Tests'
on: push
jobs:
  cypress-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0
      - uses: actions/setup-node@v3
        with:
          node-version: 19
      - name: Install dependencies
        run: npm ci
      - name: Cypress run
        uses: cypress-io/github-action@v5
        with:
          build: npm run build
          start: npm run dev